#include "execute.h"
#include "externs.h"
#include <fcntl.h>
#include <signal.h>
#include <unistd.h>
#include <sys/wait.h>

void forkexec (COMMAND *pcmd);

int execute_disk_command (void) {
    if (0 == cmd_count) {
        return 0;
    }
    if ('\0' != infile[0]) {
        cmd[0].infd = open (infile, O_RDONLY);
    }
    if ('\0' != outfile[0]) {
        if (append) {
            cmd[cmd_count - 1].outfd = open (outfile,
                            O_WRONLY | O_CREAT | O_APPEND, 0666);
        } else {
            cmd[cmd_count - 1].outfd = open (outfile,
                            O_WRONLY | O_CREAT | O_TRUNC, 0666);
        }
    }

    /* 后台作业不会调用wait()等待子进程退出 */
    /* 为避免产生僵尸进程需要忽略SIGCHLD */
    if (backgrnd) {
        signal (SIGCHLD, SIG_IGN);
    } else {
        signal (SIGCHLD, SIG_DFL);
    }

    /* ls | grep init | wc -w */
    int i;
    int fds[2];
    for (i = 0; i < cmd_count; ++i) {
        /* 如果不是最后一条命令则需创建管道 */
        if (i < cmd_count - 1) {
            pipe (fds);
            /* 将当前这条命令的标准输出定位到管道的写端 */
            cmd[i].outfd = fds[1];
            /* 将下一条命令的标准输入定位到管道的读端 */
            cmd[i+1].infd = fds[0];
        }
        forkexec (&cmd[i]);

        /* 执行其他命令之前需要关闭之前重定位的输入输出文件描述符 */
        if (STDIN_FILENO != cmd[i].infd) {
            close (cmd[i].infd);
        }
        if (STDOUT_FILENO != cmd[i].outfd) {
            close (cmd[i].outfd);
        }
    }

    if (!backgrnd) {
        /* 前台作业*/
        /* 等待管道命令中执行最后一条简单命令的子进程退出 */
        while (lastpid != wait (NULL)) {
            ;
        }
    }

    return 0;
}

void forkexec (COMMAND *pcmd) {
    pid_t pid = fork ();
    if (-1 == pid) {
        ERR_EXIT ("fork err");
    }

    if (0 < pid) {
        /* 父进程 */
        if (backgrnd) {
            printf ("%d\n", pid);
        }
        /* 保存创建出来的子进程号 */
        lastpid = pid;
    } else if (0 == pid) {
        /* 子进程 */
        /* 后台作业时将第一条件单命令的infd重定向至/dev/null */
        /* 如此，当第一条命令试图从标准输入获取数据时将立即放回EOF */
        if (0 == pcmd->infd && backgrnd) {
            pcmd->infd = open ("/dev/null", O_RDONLY);
        }
        /* 将第一个简单命令进程作为进程组组长 */
        if (pcmd == cmd) {
            setpgid (0, 0);
        }
        if (STDIN_FILENO != pcmd->infd) {
            /* 关闭该进程的标准输入 */
            close (STDIN_FILENO);
            /* 0指向命令的输入文件 */
            dup (pcmd->infd);
        }
        if (STDOUT_FILENO != pcmd->outfd) {
            /* 关闭该进程的标准输出 */
            close (STDOUT_FILENO);
            /* 1指向命令的输出文件 */
            dup (pcmd->outfd);
        }
        if (STDERR_FILENO != pcmd->errfd) {
            /* 关闭该进程的标准输出 */
            close (STDERR_FILENO);
            /* 2指向命令的错误文件 */
            dup (pcmd->errfd);
        }

        /* 将4以上的文件描述符关闭 */
        for (int i = STDERR_FILENO + 1; i < fopen_cur; ++i) {
            close (i);
        }

        /* 前台作业能接收SIGINT、SIGQUIT */
        /* 这两个信号要恢复成默认操作 */
        if (!backgrnd) {
            signal (SIGINT, SIG_DFL);
            signal (SIGQUIT, SIG_DFL);
        }
        execvp (pcmd->args[0], pcmd->args);
        /* 若替换进程失败则退出 */
        exit (EXIT_FAILURE);
    }
}